#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
    Part of Seagull - The Pyhon Pages engine
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    server_interface
      The 'server' object available from the python pages.
      Actually the gateway between this pages and the server.
"""
__author__ = 'Jonathan Slenders <jonathan at slenders dot be>'
__license__ = 'GNU LGPL'
__all__ = ('ServerInterface', 'ExitPageException', 'PageIsBinaryException', 'IncludeException')

import sys
import time
import datetime

from configuration import SystemConfig
import url_translations
import page_lookup
import page_providers
import WebPage

class ServerInterface(object):
	"""
	Server interface -- initialized for each individual request --
	Pages can interact with the server only through this class
	"""
	def __init__(self, render_input, output_buffer):
		self.start_time = time.clock() # Used for benchmarking
		self.__trace_log = [ ]

		# Fixed members, pages shouldn't change this one
		self.__render_input = render_input
		self.client_addr = render_input.client_addr
		self.query_string = render_input.query_string
		self.query_parameters = render_input.query_parameters
		self.post_data = render_input.post_data
		self.request_protocol = render_input.request_protocol
		self.request_method = render_input.request_method
		self.request_headers = render_input.request_headers
		self.requested_page = render_input.requested_page
		self.headers = render_input.headers
		self.status_code = 200

		# URL translation functions
		self.url_to_page = url_translations.url_to_page

		# Default page settings
		self.page_path = u'/' # Page to be rendered
		self.url_suffix = u'' # Empty or starts with a slash
		self.output_mode = 'xhtml'

		# Load session into dict
		self.session = { }
		if SystemConfig().session_manager:
			self.__session_id = self.__render_input.get_session_id()
			self.session = SystemConfig().session_manager.load_session(self.__session_id)

		# Output filters (functions that can manipulate the created DOM tree)
		self._filters = { } # name:str -> { params:dict }

		# CSS and Javascript buffer (Needs the 'css' and 'javascript' filter to insert into HTML tree)
		self.__css_links = [ ] # Array of <link rel="stylesheet" href="..." title="" />
		self.__javascript_links = [ ] # Array of <script type="text/javascript" src="..."></script>

		# Output buffer
		self.__output_buffer = output_buffer

		self.__buffers = [ ] # Nested buffers, for capturing

		# TODO: Make most of this fields read-only


		# Make exception classes available through the server object
		for e in __all__:
			if 'Exception' in e:
				self.__dict__[e] = eval(e)

	def page_to_url(self, url):
		return url_translations.page_to_url(url, reference_page=self.page_path)

	def page_to_full_url(self, url):
		return url_translations.page_to_full_url(url, reference_page=self.page_path)

	def has_url_suffix(self):
		return self.url_suffix not in ('', '/')
	
	def headers_have_been_send(self):
		return self.__output_buffer.headers_have_been_send()

	def dispose(self):
		"""
		For internal use only
		"""
		session_manager = SystemConfig().session_manager

		if session_manager:
			session_manager.store_session(self.__session_id)

	def get_query_parameter(self, key):
		assert(isinstance(data, basestring))

		if self.__render_input.query_parameters.has_key(key):
			return self.__render_input.query_parameters[key]
		else:
			return ''
	
	def set_expiry_date(self, date):
		"""
		Set the expires header. (for caching control in the browser)
		'date' should be a datetime object.
		"""
		assert(isinstance(date, datetime.datetime))

		self.headers['Expires'] = date.strftime(self.__render_input.http_header_time_format)

	def send_headers(self):
		"""
		When a page requests to send his headers. *Only* use this
		function from inside pages when outputting in raw mode.
		"""
		if not 'Content-Type' in self.headers:
			raise Exception('No content-Type header defined')

		self.__output_buffer.write_headers(self.headers, self.status_code)

	def set_status_code(self, status_code):
		""" Set HTTP status code """
		assert isinstance(status_code, int)
		self.status_code = status_code

	def flush_output(self):
		"""
		Makes sure that all output data is send to the client
		"""
		self.__output_buffer.flush()

	def trace(self, data):
		"""
		Log debug messages. These will be shown in case of error.
		"""
		assert(isinstance(data, basestring))

		self.__trace_log.append(TraceMessage(data))
		self.__render_input.trace(data)

	def get_trace(self):
		"""
		Return current trace log
		"""
		return self.__trace_log

	def set_redirect(self, location):
		""" Sets a 301 Redirect header """
		assert(isinstance(location, basestring))

		self.status_code = 301
		self.headers['Location'] = location

	def start_output_buffer(self):
		""" Start capturing the output stream """
		buffer = StringOutputBuffer()
		sys.stdout.register_stdout(buffer) # We suppose stdout has been replaced in seagull.renderer
		self.__buffers.append(buffer)

	def flush_output_buffer(self):
		""" Flush the output stream to a string """
		if len(self.__buffers) > 0:
			buffer = self.__buffers.pop()
			sys.stdout.delete_stdout() # We suppose stdout has been replaced in seagull.renderer

			return buffer.get_buffer_content()
		else:
			raise Exception('Please call start_output_buffer first')

	def has_output_buffer(self):
		""" Returns true when the output is captured in a buffer """
		return len(self.__buffers) > 0

	def add_filter(self, filter, parameters={ }):
		""" Add an output filter """
		assert isinstance(filter, basestring)
		assert isinstance(parameters, dict)

		# When the filter was added earlier, update only parameters
		if filter in self._filters:
			self._filters[filter].update(parameters)
		else:
			self._filters[filter] = parameters.copy()

	def get_filters(self):
		""" yields filtername, parameters_dict """
		for f in self._filters.keys():
			yield f, self._filters[f]

	def debug_interrupt(self, path, line, column, globals, locals):
		"""
		Pauses the execution of a page, for debugging.
		This may be used as a breakpoint.
		Should only be called from generated (debug)code.
		"""
		self.__render_input.debug_interrupt(path, line, column, globals, locals)

	def exit(self):
		"""
		Stops rendering
		"""
		raise ExitPageException();

	def add_css(self, abs_path):
		"""
		Add a Css file 
		"""
		if abs_path not in self.__css_links:
			self.__css_links.append(abs_path)

	def get_css(self): return self.__css_links

	def add_javascript(self, abs_path):
		"""
		Add a javascript fle
		"""
		if abs_path not in self.__javascript_links:
			self.__javascript_links.append(abs_path)

	def get_javascript(self): return self.__javascript_links

class TraceMessage(object):
	def __init__(self, message):
		self.message = message
		self.time = time.localtime()

################################################################
# Exceptions
################################################################

class ExitPageException(Exception):
	"""
	Raised when leaving page rendering.
	"""
	def __init__(self):
		Exception.__init__(self)


class PageIsBinaryException(Exception):
	"""
	Received a binary page, while expecting a python page
	"""
	def __init__(self):
		Exception.__init__(self)

class IncludeException(Exception):
	"""
	Including this page failed.
	"""
	def __init__(self, msg):
		Exception.__init__(self, msg)

################################################################
# NestedBuffer
################################################################

class StringOutputBuffer(object):
	"""
	Used to capture stdout
	Allows writing of both str and unicode.
	But returns always a utf-8 encoded str object.
	"""
	def __init__(self):
		self.__buffer = [ ]
	
	def write(self, string):
		if string.__class__.__name__ == 'unicode':
			self.__buffer.append(string.encode('utf-8'))
		else:
			self.__buffer.append(string)

	def get_buffer_content(self):
		return ''.join(self.__buffer)
